#include "defines.h"
#include "lib.h"
#include "kozos.h"
#include "kernel.h"
#include "bitsearch.h"

typedef struct _kz_msgbuf {
    struct _kz_msgbuf *next;
    kz_thread *sender;
    struct {
        int size;
        char *p;
    } param;
} kz_msgbuf;

typedef struct _kz_msgbox {
    kz_thread *receiver; // ボックスごとに受信者は1人とする。
    kz_msgbuf *head[MSGBOX_PRIORITY_NUM];
    kz_msgbuf *tail[MSGBOX_PRIORITY_NUM];
    uint32 bitmap;
    int num[MSGBOX_PRIORITY_NUM];
} kz_msgbox;

static kz_msgbox *msgboxes[MSGBOX_NUM];

void resource_msgbox(kz_resourceinfo_t *infop)
{
    int c = 0;
    kz_msgbox **q;

    for (q = msgboxes; q < msgboxes + MSGBOX_NUM; q++) {
        if (*q) ++c;
    }

    infop->type = RESOURCE_MSGBOX;
    infop->num = MSGBOX_NUM;
    infop->used = c;
}

void ipc_init()
{
    ASSERT(memeq(msgboxes, 0, sizeof(msgboxes)), "BSS isn't cleared");
}

kz_msgbox_id_t msgbox_get()
{
    int i;
    for (i = 0; i < MSGBOX_NUM; i++) {
        if (!msgboxes[i]) {
            msgboxes[i] = kz_kmalloc(sizeof(kz_msgbox));
            memset(msgboxes[i], 0, sizeof(kz_msgbox));
            return i;
        }
    }

    ASSERT(0, "msgbox_get: no free boxes");
    return MSGBOX_NULL;
}

void msgbox_free(kz_msgbox_id_t id)
{
    kz_msgbox *mb;

    ASSERT(msg_arrived(id) == -1, "msgbox_free: box isn't empty");

    mb = msgboxes[id];
    msgboxes[id] = NULL;

    if (mb->receiver) {
        thread_wakeup(mb->receiver);
    }

    kz_kmfree(mb);
}

int pushmsg(kz_msgbox_id_t id, int size, char *p, int priority, int maxnum, kz_thread *sender)
{
    kz_msgbuf *mp;
    kz_msgbox *mboxp;

    ASSERT(0 <= id && id < MSGBOX_NUM, "pushmsg: invalid ID");
    mboxp = msgboxes[id];
    ASSERT(mboxp, "pushmsg: box isn't used");
    ASSERT(0 <= priority && priority < MSGBOX_PRIORITY_NUM, "pushmsg: invalid priority");
    ASSERT(!mboxp->receiver, "pushmsg: receiver exists");

    if (maxnum && mboxp->num[priority] >= maxnum) {
        return -1;
    }

    mp = (kz_msgbuf *)kzmem_alloc(sizeof(*mp));
    mp->next = NULL;
    mp->sender = sender;
    mp->param.size = size;
    mp->param.p = p;

    if (mboxp->tail[priority]) {
        mboxp->tail[priority]->next = mp;
    } else {
        mboxp->head[priority] = mp;
    }
    mboxp->tail[priority] = mp;
    mboxp->num[priority]++;
    mboxp->bitmap |= bit32(priority);

    return 0;
}

kz_thread_id_t popmsg(kz_msgbox_id_t id, int priority, int *sizep, char **pp)
{
    kz_msgbox *mboxp;
    kz_msgbuf *mp;
    kz_thread_id_t ret;

    ASSERT(0 <= id && id < MSGBOX_NUM, "popmsg: invalid ID");
    mboxp = msgboxes[id];
    ASSERT(mboxp, "popmsg: box isn't used");
    ASSERT(!mboxp->receiver, "popmsg: receiver exists");
    ASSERT(0 <= priority && priority < MSGBOX_PRIORITY_NUM, "popmsg: Bad priority");
    ASSERT(mboxp->head[priority], "popmsg: box is empty");

    mp = mboxp->head[priority];
    mboxp->head[priority] = mp->next;
    if (!mboxp->head[priority]) {
        mboxp->tail[priority] = NULL;
        mboxp->bitmap &= ~bit32(priority);
    }
    mboxp->num[priority]--;

    ret = mp->sender ? mp->sender->id : THREAD_ID_NULL;
    if (sizep)
        *sizep = mp->param.size;
    if (pp)
        *pp = mp->param.p;

    kzmem_free(mp);
    return ret;
}

kz_thread_id_t recvmsg(kz_msgbox_id_t id, int priority, kz_thread *receiver)
{
    kz_syscall_param_t *param;

    ASSERT(receiver, "recvmsg: receiver is NULL");

    param = receiver->syscall.param;
    param->un.recv.ret = popmsg(id, priority, param->un.recv.sizep, param->un.recv.pp);

    return param->un.recv.ret;
}

int msg_arrived(kz_msgbox_id_t id)
{
    kz_msgbox *mboxp;
    int i;

    ASSERT(0 <= id && id < MSGBOX_NUM, "msg_arrived: invalid ID");
    mboxp = msgboxes[id];
    ASSERT(mboxp, "msg_arrived: box isn't used");

    i = bitsearch(mboxp->bitmap);
    ASSERT(i == -1 || (mboxp->head[i] && !mboxp->receiver), "msg_arrived: mbox error");

    return i;
}

void setreceiver(kz_msgbox_id_t id, kz_thread *receiver)
{
    kz_msgbox *mboxp;

    ASSERT(0 <= id && id < MSGBOX_NUM, "setreceiver: invalid ID");
    mboxp = msgboxes[id];
    ASSERT(mboxp, "setreceiver: box isn't used");
    ASSERT(receiver, "setreceiver: receiver is NULL");
    ASSERT(!mboxp->receiver, "setreceiver: receiver exists");

    receiver->flags |= THREAD_WAITMSG;
    mboxp->receiver = receiver;
}

kz_thread *getreceiver(kz_msgbox_id_t id)
{
    kz_msgbox *mboxp;

    ASSERT(0 <= id && id < MSGBOX_NUM, "getreceiver: invalid ID");
    mboxp = msgboxes[id];
    ASSERT(mboxp, "getreceiver: box isn't used");
    return mboxp->receiver;
}

void cancelreceiver(kz_msgbox_id_t id)
{
    kz_msgbox *mboxp;
    kz_syscall_param_t *param;

    ASSERT(0 <= id && id < MSGBOX_NUM, "cancelreceiver: invalid ID");
    mboxp = msgboxes[id];
    ASSERT(mboxp, "cancelreceiver: box isn't used");

    ASSERT(mboxp->receiver, "cancelreceiver: receiver isn't waiting");
    param = mboxp->receiver->syscall.param;
    param->un.recv.ret = THREAD_ID_ERR;

    mboxp->receiver->flags &= ~THREAD_WAITMSG;
    mboxp->receiver = NULL;
}

void sendreceiver(kz_msgbox_id_t id, kz_thread *sender, int size, char *p)
{
    kz_msgbox *mboxp;
    kz_syscall_param_t *param;

    ASSERT(0 <= id && id < MSGBOX_NUM, "cancelreceiver: invalid ID");
    mboxp = msgboxes[id];
    ASSERT(mboxp, "cancelreceiver: box isn't used");

    ASSERT(mboxp->receiver, "cancelreceiver: receiver isn't waiting");
    param = mboxp->receiver->syscall.param;
    param->un.recv.ret = sender ? sender->id : THREAD_ID_NULL;
    if (param->un.recv.sizep) *param->un.recv.sizep = size;
    if (param->un.recv.pp) *param->un.recv.pp = p;

    mboxp->receiver->flags &= ~THREAD_WAITMSG;
    mboxp->receiver = NULL;
}
